using System;
using System.Collections.Generic;

using Pegasus.Diagnostics;

namespace Pegasus.Workflow.Service
{
	/// <summary>
	/// Abstract base class for all workflows.
	/// </summary>
	public abstract class Workflow
	{
		// Local Instance Values
		private int m_workflowId = -1;
		private string m_name;
		private string m_displayName;
		private string m_description;
		private WorkflowContext m_context;

		private string m_initialState = null;
		private string m_currentState = null;
		private Dictionary<string, WorkflowState> m_stateTable = new Dictionary<string, WorkflowState>();

		private bool m_hasCompleted = false;
		private int m_exitCode = 0;

		private WorkflowStatus m_currentStatus = WorkflowStatus.Unknown;

		// You must lock this object before accessing it.
		private Stack<WorkflowStatus> m_statusStack = new Stack<WorkflowStatus>();

		// Local Const Values
		private const string ContextCurrentState = "WorkflowContext.CurrentWorkflowState";
		private const string ContextChildWorkflowIds = "WorkflowContext.ChildWorkflowIds";
		private const string ContextCompletedEventHandlers = "WorkflowContext.CompletedEventHandlers";
		private const string ContextCompleted = "WorkflowContext.WorkflowCompleted";
		private const string ContextExitCode = "WorkflowContext.ExitCode";

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Workflow"/> class.
		/// </summary>
		public Workflow( string workflowName ) :
			this( workflowName, null, null )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Workflow"/> class.
		/// </summary>
		/// <param name="workflowName">Name of the workflow.</param>
		/// <param name="displayName">Name of the display.</param>
		/// <param name="description">The description.</param>
		public Workflow( string workflowName, string displayName, string description )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( workflowName, "workflowName" );
			// displayName can be null
			// description can be null

			m_name = workflowName;
			m_displayName = displayName;
			m_description = description;
		}

		/// <summary>
		/// Gets the workflow id.
		/// </summary>
		/// <value>The workflow id.</value>
		public int WorkflowId
		{
			get
			{
				return m_workflowId;
			}
		}

		/// <summary>
		/// Gets the name of the workflow.
		/// </summary>
		/// <value>The name.</value>
		public string Name
		{
			get
			{
				return m_name;
			}
		}

		/// <summary>
		/// Gets the display name the workflow.
		/// </summary>
		/// <value>The display name.</value>
		public string DisplayName
		{
			get
			{
				return m_displayName;
			}
		}

		/// <summary>
		/// Gets the description.
		/// </summary>
		/// <value>The description.</value>
		public string Description
		{
			get
			{
				return m_description;
			}
		}

		/// <summary>
		/// Gets the context for this workflow.
		/// </summary>
		/// <value>The context.</value>
		public WorkflowContext Context
		{
			get
			{
				return m_context;
			}
		}

		/// <summary>
		/// Gets the initial state name.
		/// </summary>
		/// <value>The initial state name.</value>
		public string InitialState
		{
			get
			{
				return m_initialState;
			}
		}

		/// <summary>
		/// Gets the name of the current state.
		/// </summary>
		/// <value>The current state name.</value>
		public string CurrentState
		{
			get
			{
				return m_currentState;
			}
		}

		/// <summary>
		/// Gets the states a table of the workflow states, indexed by their unique name.
		/// </summary>
		/// <value>The states.</value>
		public Dictionary<string, WorkflowState> States
		{
			get
			{
				return m_stateTable;
			}
		}

		/// <summary>
		/// Gets a value indicating whether this instance has completed.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance has completed; otherwise, <c>false</c>.
		/// </value>
		public bool HasCompleted
		{
			get
			{
				return m_hasCompleted;
			}
		}

		/// <summary>
		/// Gets or sets the exit code.
		/// </summary>
		/// <value>The exit code.</value>
		public int ExitCode
		{
			get
			{
				return m_exitCode;
			}

			set
			{
				ThrowIfReadOnly();
				m_exitCode = value;
			}
		}

		/// <summary>
		/// Gets the child workflows ids.
		/// </summary>
		/// <value>The child workflows ids.</value>
		public List<int> ChildWorkflowsIds
		{
			get
			{
				List<int> listCopy = new List<int>();

				// Create a copy of the child workflow Ids
				if( m_context.ContainsKey( ContextChildWorkflowIds ) )
				{
					listCopy.AddRange( (List<int>) m_context[ ContextChildWorkflowIds ] );
				}

				return listCopy;
			}
		}

		/// <summary>
		/// Sets the startup args for the workflow.
		/// </summary>
		/// <param name="args">The args.</param>
		/// <remarks>
		///	This method is called prior to the workflow begin registered with the 
		/// persistence service and entering into it's initial state.  This is called 
		/// sync as part of the workflow startup process.  So the workflow ID has
		/// not been assigned at the point where this method is called.
		/// 
		/// If you throw an exception the workflow startup process will be halted
		/// and the worklfow will not be created.
		/// </remarks>
		public virtual void SetStartupArgs( object[] args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( args, "args" );
		}

		/// <summary>
		/// Registers the states.
		/// </summary>
		public abstract void RegisterStates();

		/// <summary>
		/// Gets the service.
		/// </summary>
		/// <returns></returns>
		public T GetService<T>()
		{
			return (T) GetService( typeof( T ) );
		}

		/// <summary>
		/// Gets the service object of the specified type.
		/// </summary>
		/// <param name="serviceType">An object that specifies the type of service object to get.</param>
		/// <returns>
		/// A service object of type serviceType.-or- null if there is no service object of type serviceType.
		/// </returns>
		public object GetService( Type serviceType )
		{
			// Check Parameters
			ParamCode.AssertNotNull( serviceType, "serviceType" );

			return m_context.GetService( serviceType );
		}

		/// <summary>
		/// Sets the current state of the workflow.  The method will cause a state change between the current
		/// state and the given state.
		/// </summary>
		/// <param name="stateName">Name of the state.</param>
		public void SetState( string stateName )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( stateName, "stateName" );

			ThrowIfReadOnly();

			try
			{
				PushStatus( WorkflowStatus.Running );

				// Make sure we have a valid new state
				if( !m_stateTable.ContainsKey( stateName ) )
				{
					throw new ArgumentException( "Invalid state name, no such state registed." );
				}

				// Call the exit/clean up methods.
				if( !string.IsNullOrEmpty( m_currentState ) )
				{
					CallExitState( m_currentState, stateName );
				}

				// Set the new state
				string previousStateName = m_currentState;
				m_currentState = stateName;
				
				// Call the enter methods.
				if( !string.IsNullOrEmpty( m_currentState ) )
				{
					CallEnterState( m_currentState, previousStateName );
				}
			}
			finally
			{
				PopStatus();
			}
		}

		/// <summary>
		/// Saves this instance.
		/// </summary>
		public void Save()
		{
			m_context.Save( false );
		}

		/// <summary>
		/// Saves the specified unlock.
		/// </summary>
		/// <param name="unlock">if set to <c>true</c> will save an then unlock the context.</param>
		public void Save( bool unlock )
		{
			m_context.Save( unlock );
		}

		/// <summary>
		/// Registers the timer.
		/// </summary>
		/// <param name="time">The time.</param>
		/// <param name="timerHandler">The timer handler.</param>
		/// <returns>The id of the timer</returns>
		public int RegisterTimer( DateTime time, EventHandler<WorkflowEventArgs> timerHandler )
		{
			// Check Parameters
			// time can be any date time (if in the past fires now)
			ParamCode.AssertNotNull( timerHandler, "timerHandler" );

			return RegisterTimer( time, timerHandler, null, null );
		}

		/// <summary>
		/// Registers the timer.
		/// </summary>
		/// <param name="time">The time.</param>
		/// <param name="deliverToCurrentStateOnly"><c>true</c> if deliver to current state only; otherwise delive to all listeners, <c>false</c>.</param>
		/// <param name="timerHandler">The timer handler.</param>
		/// <returns></returns>
		/// <remarks>
		/// When delivering the timer event to the given workflow if deliverToCurrentStateOnly is <c>true</c> then 
		/// the event will only be delivered to listeners/deligates that are on the current state class or
		/// the workflow class.  All other listeners will be ignored.
		/// </remarks>
		public int RegisterTimer( DateTime time, bool deliverToCurrentStateOnly, EventHandler<WorkflowEventArgs> timerHandler )
		{
			// Check Parameters
			// time can be any date time (if in the past fires now)
			ParamCode.AssertNotNull( timerHandler, "timerHandler" );

			return RegisterTimer( time, timerHandler, null, new WorkflowEventArgs( m_workflowId, deliverToCurrentStateOnly ) );
		}

		/// <summary>
		/// Registers the timer.
		/// </summary>
		/// <param name="time">The time.</param>
		/// <param name="timerHandler">The timer handler.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service2.WorkflowEventArgs"/> instance containing the event data.</param>
		/// <returns>The id of the timer</returns>
		public int RegisterTimer( DateTime time, EventHandler<WorkflowEventArgs> timerHandler, object sender, WorkflowEventArgs args )
		{
			// Check Parameters
			// time can be any date time (if in the past fires now)
			ParamCode.AssertNotNull( timerHandler, "timerHandler" );
			// sender can be null
			// args can be null

			WorkflowMessageService msgService = GetService<WorkflowMessageService>();
			WorkflowMessageService.WorkflowEventMessage message = msgService.BuildWorkflowEventMessage( timerHandler );

			if( args == null )
			{
				args = new WorkflowEventArgs( message.WorkflowId );
			}

			return msgService.RegisterWorkflowEventMessage( time, message, sender, args );
		}

		/// <summary>
		/// Releases the timer.
		/// </summary>
		/// <param name="timerId">The timer id that was returned by RegisterTimer() method.</param>
		public void ReleaseTimer( int timerId )
		{
			// Check Parameters
			ParamCode.AssertRange( timerId, 0, int.MaxValue, "timerId" );

			GetService<WorkflowMessageService>().ReleaseWorkflowEventMessage( timerId );
		}

		/// <summary>
		/// Records the tracking message.
		/// </summary>
		/// <param name="messageName">Name of the message.</param>
		/// <param name="args">The args.</param>
		public void RecordTrackingMessage( string messageName, params object[] args )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( messageName, "messageName" );
			ParamCode.AssertNotNull( args, "args" );

			GetService<WorkflowTrackingService>().RecordCustomMessage( m_context, messageName, args );
		}

		/// <summary>
		/// Records the tracking message XML.
		/// </summary>
		/// <param name="messageName">Name of the message.</param>
		/// <param name="xml">The XML.</param>
		public void RecordTrackingMessageXml( string messageName, string xml )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( messageName, "messageName" );
			ParamCode.AssertNotEmpty( xml, "xml" );

			GetService<WorkflowTrackingService>().RecordCustomMessageXml( m_context, messageName, xml );
		}


		/// <summary>
		/// Restarts the workflow.
		/// </summary>
		/// <param name="startState">The start state.</param>
		public void RestartWorkflow( string startState )
		{
			m_exitCode = 0;
			m_hasCompleted = false;

			Save();

			GetService<WorkflowMessageService>().SendWorkflowRestartMessage( m_workflowId, startState );
		}

		/// <summary>
		/// Starts the child workflow.
		/// </summary>
		/// <param name="workflowType">Type of the workflow.</param>
		/// <param name="args">The args.</param>
		/// <returns>The workflow id of the new workflow.</returns>
		public int StartChildWorkflow( Type workflowType, params object[] args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( workflowType, "workflowType" );
			ParamCode.AssertNotNull( args, "args" );

			ThrowIfReadOnly();

			return StartChildWorkflowEx( workflowType, null, args );
		}

		/// <summary>
		/// Starts the child workflow.
		/// </summary>
		/// <param name="workflowType">Type of the workflow.</param>
		/// <param name="completedHandler">The completed handler.</param>
		/// <param name="args">The args.</param>
		/// <returns>The workflow id of the new workflow.</returns>
		public int StartChildWorkflowEx( Type workflowType, EventHandler<WorkflowEventArgs> completedHandler, params object[] args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( workflowType, "workflowType" );
			// completedHandler can be null
			ParamCode.AssertNotNull( args, "args" );

			ThrowIfReadOnly();

			// Start the child workflow
			int childWorkflowId = m_context.WorkflowService.StartWorkflowEx( workflowType, completedHandler, args );

			// Create a list for holding the child workflow ids
			if( !m_context.ContainsKey( ContextChildWorkflowIds ) )
			{
				m_context.Add( ContextChildWorkflowIds, new List<int>() );
			}

			// Add the child workflow to the list.
			List<int> childWorkflowIdList = (List<int>) m_context[ ContextChildWorkflowIds ];
			childWorkflowIdList.Add( childWorkflowId );

			return childWorkflowId;
		}


		/// <summary>
		/// Workflows the complete.
		/// </summary>
		public void WorkflowComplete()
		{
			ThrowIfReadOnly();

			WorkflowComplete( m_exitCode );
		}

		/// <summary>
		/// Workflows the complete.
		/// </summary>
		/// <param name="exitCode">The exit code.</param>
		public void WorkflowComplete( int exitCode )
		{
			ThrowIfReadOnly();

			m_exitCode = exitCode;
			m_hasCompleted = true;
			m_currentState = string.Empty;

			// Get the completed event handlers and dispatch the messages
			if( m_context.ContainsKey( ContextCompletedEventHandlers ) )
			{
				WorkflowMessageService msgService = m_context.GetService<WorkflowMessageService>();

				List<WorkflowMessageService.WorkflowEventMessage> completedEventHandlerList = 
					(List<WorkflowMessageService.WorkflowEventMessage>) m_context[ ContextCompletedEventHandlers ];

				foreach( WorkflowMessageService.WorkflowEventMessage message in completedEventHandlerList )
				{
					msgService.SendWorkflowEventMessage( message, null, new WorkflowEventArgs( m_workflowId ) );
				}
			}

			FireCompleted();
		}

		/// <summary>
		/// Fires the on exception occurred.
		/// </summary>
		/// <param name="e">The exception.</param>
		public void FireUnhandledException( Exception e )
		{
			// Check Parameters
			ParamCode.AssertNotNull( e, "e" );

			// Record the exception with the tracking service
			m_context.GetService<WorkflowTrackingService>().RecordException( m_context, e );

			OnUnhandledException( e );
		}

		/// <summary>
		/// Initializes the specified context.
		/// </summary>
		/// <param name="context">The context.</param>
		/// <param name="workflowId">The workflow id.</param>
		internal void Initialize( WorkflowContext context, int workflowId )
		{
			// Check Parameters
			ParamCode.AssertNotNull( context, "context" );
			ParamCode.AssertRange( workflowId, 0, int.MaxValue, "workflowId" );

			m_context = context;
			m_workflowId = workflowId;

			m_stateTable.Clear();
			RegisterStates();
			if( string.IsNullOrEmpty( m_initialState ) )
			{
				throw new WorkflowException( "Initialize of workflow {0} failed, no initial state was specified", GetType() );
			}

			// Get Local values out of the context object
			if( !context.TryGetValue<bool>( ContextCompleted, out m_hasCompleted ) )
			{
				// for legacy script that use string as completed type
				string completed = string.Empty;
				if( !context.TryGetValue<string>( ContextCompleted, out completed ) )
				{
					m_hasCompleted = false;
				}
				else
				{
					if( !bool.TryParse( completed, out m_hasCompleted ) )
					{
						m_hasCompleted = false;
					}
				}
			}

			if( !context.TryGetValue<int>( ContextExitCode, out m_exitCode ) )
			{
				m_exitCode = 0;
			}

			if( !context.TryGetValue<string>( ContextCurrentState, out m_currentState ) )
			{
				m_currentState = null;
			}

			// Make sure we have a valid current state
			if( !string.IsNullOrEmpty( m_currentState ) )
			{
				if( !m_stateTable.ContainsKey( m_currentState ) )
				{
					throw new WorkflowException( "Initialize of workflow {0} failed, invalid current state {0} was found.", GetType(), m_currentState );
				}
			}

			// Update the context with the new values.
			if( !context.IsReadOnly )
			{
				context[ ContextCurrentState ] = m_currentState;
				context[ ContextCompleted ] = m_hasCompleted;
				context[ ContextExitCode ] = m_exitCode;
			}
		}

		/// <summary>
		/// Registers the complete handler.
		/// </summary>
		/// <param name="completedHandler">The completed handler.</param>
		public void RegisterCompleteHandler( EventHandler<WorkflowEventArgs> completedHandler )
		{
			// Check Parameters
			ParamCode.AssertNotNull( completedHandler, "completedHandler" );

			WorkflowMessageService msgService = GetService<WorkflowMessageService>();
			WorkflowMessageService.WorkflowEventMessage workflowEventMsg = 
				msgService.BuildWorkflowEventMessage( completedHandler );

			// Create a list for holding the completed event handlers information.
			if( !m_context.ContainsKey( ContextCompletedEventHandlers ) )
			{
				m_context.Add( ContextCompletedEventHandlers, new List<WorkflowMessageService.WorkflowEventMessage>() );
			}

			// Add the info object to the list.
			List<WorkflowMessageService.WorkflowEventMessage> completedEventHandlerList = 
				(List<WorkflowMessageService.WorkflowEventMessage>) m_context[ ContextCompletedEventHandlers ];

			completedEventHandlerList.Add( workflowEventMsg );
		}

		/// <summary>
		/// Pushes the the current status on to the stack and sets the current status to the given status.
		/// </summary>
		/// <param name="status">The status.</param>
		internal void PushStatus( WorkflowStatus status )
		{
			lock( m_statusStack )
			{
				WorkflowStatus oldStatus = m_currentStatus;

				m_statusStack.Push( m_currentStatus );
				m_currentStatus = status;

				FireStatusChanged( oldStatus, m_currentStatus );
			}
		}

		/// <summary>
		/// Pops a status off the stack and assigns it to the current status.
		/// </summary>
		internal void PopStatus()
		{
			lock( m_statusStack )
			{
				WorkflowStatus oldStatus = m_currentStatus;

				m_currentStatus = m_statusStack.Pop();

				FireStatusChanged( oldStatus, m_currentStatus );
			}
		}

		/// <summary>
		/// Gets the default status.
		/// </summary>
		/// <returns></returns>
		internal WorkflowStatus GetDefaultStatus()
		{
			lock( m_statusStack )
			{
				WorkflowStatus[] statusList = m_statusStack.ToArray();
				int count = statusList.Length;
				if( count > 0 )
				{
					return statusList[ count - 1 ];
				}
				else
				{
					return m_currentStatus;
				}
			}
		}

		/// <summary>
		/// Sets the default status for this this workflow.
		/// </summary>
		/// <param name="newStatus">The new status.</param>
		/// <remarks>
		/// The default status is the last status on the stack or if there are no items
		/// on the stack then it is the current status.
		/// </remarks>
		internal void SetDefaultStatus( WorkflowStatus newStatus )
		{
			lock( m_statusStack )
			{
				WorkflowStatus[] statusList = m_statusStack.ToArray();
				int count = statusList.Length;
				if( count > 0 )
				{
					// Ok replace the last status on the stack
					int lastItem = count - 1;
					statusList[ lastItem ] = newStatus;

					// Reload the stack
					m_statusStack.Clear();
					for( int x = lastItem; x > -1; x-- )
					{
						m_statusStack.Push( statusList[ x ] );
					}
				}
				else
				{
					// Nothing on the stack so just set the current status.
					WorkflowStatus oldStatus = m_currentStatus;
					m_currentStatus = newStatus;

					// If we are changing the status then fire the status changed event.
					if( m_currentStatus != oldStatus )
					{
						FireStatusChanged( oldStatus, m_currentStatus );
					}
				}
			}
		}

		/// <summary>
		/// Fires the on initialize complete event.
		/// </summary>
		internal void FireInitializeComplete()
		{
			OnIntializeComplete();
		}

		/// <summary>
		/// Fires the restarted.
		/// </summary>
		internal void FireRestarted()
		{
			OnRestarted();
		}

		/// <summary>
		/// Fires the on status changed.
		/// </summary>
		/// <param name="oldStatus">The old status.</param>
		/// <param name="newStatus">The new status.</param>
		internal void FireStatusChanged( WorkflowStatus oldStatus, WorkflowStatus newStatus )
		{
			OnStatusChanged( oldStatus, newStatus );
		}

		/// <summary>
		/// Fires the on state enter events.
		/// </summary>
		/// <param name="previousState">Previouse state, null if workflow is just starting.</param>
		internal void FireStateEnter( string previousState )
		{
			OnStateEnter( previousState );
		}

		/// <summary>
		/// Fires the on state exit events.
		/// </summary>
		/// <param name="nextState">Next state, null if workflow is finishing.</param>
		internal void FireStateExit( string nextState )
		{
			OnStateExit( nextState );
		}

		/// <summary>
		/// Fires the before save.
		/// </summary>
		internal void FireBeforeSave()
		{
			OnBeforeSave();
		}

		/// <summary>
		/// Fires the workflow completed event.
		/// </summary>
		internal void FireCompleted()
		{
			GetService<WorkflowTrackingService>().RecordWorkflowCompleted( m_context );
			OnCompleted();
		}

		/// <summary>
		/// Sets the initial state.
		/// </summary>
		/// <param name="stateName">Name of the state.</param>
		protected void SetInitialState( string stateName )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( stateName, "stateName" );

			if( !m_stateTable.ContainsKey( stateName ) )
			{
				throw new WorkflowException( "Unknown initial state {0}, state not registered", stateName );
			}

			m_initialState = stateName;
		}

		/// <summary>
		/// Adds the state.
		/// </summary>
		/// <param name="newState">The new state.</param>
		protected void RegisterState( WorkflowState newState )
		{
			// Check Parameters
			ParamCode.AssertNotNull( newState, "newState" );

			if( m_stateTable.ContainsKey( newState.Name ) )
			{
				throw new WorkflowException( "State with name {0} already registered.", newState.Name );
			}

			newState.SetWorkflow( this );
			m_stateTable.Add( newState.Name, newState );
		}

		/// <summary>
		/// Called when intialization of the workflow is complete.
		/// </summary>
		protected virtual void OnIntializeComplete()
		{
			foreach( WorkflowState state in m_stateTable.Values )
			{
				state.FireIntializeComplete();
			}

			if( IntializeComplete != null )
			{
				IntializeComplete( this, new WorkflowEventArgs( m_workflowId ) );
			}
		}

		/// <summary>
		/// Called when [restarted].
		/// </summary>
		protected virtual void OnRestarted()
		{
			if( Restarted != null )
			{
				Restarted( this, new WorkflowEventArgs( m_workflowId ) );
			}
		}

		/// <summary>
		/// Called when status of the workflow has changed.
		/// </summary>
		/// <param name="oldStatus">The old status.</param>
		/// <param name="newStatus">The new status.</param>
		protected virtual void OnStatusChanged( WorkflowStatus oldStatus, WorkflowStatus newStatus )
		{
			if( StatusChanged != null )
			{
				StatusChanged( this, new WorkflowStatusEventArgs( m_workflowId, oldStatus, newStatus ) );
			}
		}

		/// <summary>
		/// Called when the state is entered.
		/// </summary>
		/// <param name="previousState">Previouse state, null if workflow is just starting.</param>
		protected virtual void OnStateEnter( string previousState )
		{
			if( StateEnter != null )
			{
				StateEnter( this, new WorkflowStateEventArgs( m_workflowId, previousState ) );
			}
		}

		/// <summary>
		/// Called when the state exiting.
		/// </summary>
		/// <param name="nextState">Next state, null if workflow is finishing.</param>
		protected virtual void OnStateExit( string nextState )
		{
			if( StateExit != null )
			{
				StateExit( this, new WorkflowStateEventArgs( m_workflowId, nextState ) );
			}
		}

		/// <summary>
		/// Called when [before save].
		/// </summary>
		protected virtual void OnBeforeSave()
		{
			// Save local values to the context object
			m_context[ ContextCurrentState ] = m_currentState;
			m_context[ ContextCompleted ] = m_hasCompleted;
			m_context[ ContextExitCode ] = m_exitCode;

			// Notify the states that we are about to save
			foreach( WorkflowState state in m_stateTable.Values )
			{
				state.FireBeforeSave();
			}

			if( BeforeSave != null )
			{
				BeforeSave( this, new WorkflowEventArgs( m_workflowId ) );
			}
		}

		/// <summary>
		/// Called when the workflow has completed.
		/// </summary>
		protected virtual void OnCompleted()
		{
			if( Completed != null )
			{
				Completed( this, new WorkflowEventArgs( m_workflowId ) );
			}
		}

		/// <summary>
		/// Called when an exception occurred on the given workflow.
		/// </summary>
		/// <param name="e">The e.</param>
		protected virtual void OnUnhandledException( Exception e )
		{
			// Check Parameters
			ParamCode.AssertNotNull( e, "e" );

			if( UnhandledException != null )
			{
				UnhandledException( this, new WorkflowExceptionEventArgs( m_workflowId, e ) );
			}
		}

		/// <summary>
		/// Calls the state enter events.
		/// </summary>
		/// <param name="stateName">Name of the state.</param>
		/// <param name="previousStateName">Name of the previous state.</param>
		private void CallEnterState( string stateName, string previousStateName )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( stateName, "stateName" );
			// previousStateName can be null (when the workflow first starts)

			if( !m_stateTable.ContainsKey( stateName ) )
			{
				throw new WorkflowException( "Unknown state {0}", stateName );
			}

			// Fire the on state enter event.
			FireStateEnter( previousStateName );
			m_stateTable[ stateName ].FireStateEnter( previousStateName );

			Save();
		}

		/// <summary>
		/// Calls the state exit events.
		/// </summary>
		/// <param name="stateName">Name of the state.</param>
		/// <param name="nextStateName">Name of the next state.</param>
		private void CallExitState( string stateName, string nextStateName )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( stateName, "stateName" );
			// nextStateName can be null (when the workflow finishes)

			if( !m_stateTable.ContainsKey( stateName ) )
			{
				throw new WorkflowException( "Unknown state {0}", stateName );
			}

			// Fire the on state exit event
			FireStateExit( nextStateName );
			m_stateTable[ stateName ].FireStateExit( nextStateName );

			Save();
		}

		/// <summary>
		/// Throws if read only.
		/// </summary>
		private void ThrowIfReadOnly()
		{
			if( m_context.IsReadOnly )
			{
				throw new WorkflowException( "Context and workflow objects are read only" );
			}
		}

		/// <summary>
		/// Fired when the initialize of the workflow is complete.
		/// </summary>
		public event EventHandler<WorkflowEventArgs> IntializeComplete;

		/// <summary>
		/// Fired when the workflow has been restarted after it has completed
		/// </summary>
		public event EventHandler<WorkflowEventArgs> Restarted;

		/// <summary>
		/// Fired when the status of the workflow changes.
		/// </summary>
		public event EventHandler<WorkflowStatusEventArgs> StatusChanged;

		/// <summary>
		/// Fired when a new state is entered.
		/// </summary>
		public event EventHandler<WorkflowStateEventArgs> StateEnter;

		/// <summary>
		/// Fired when the current state is exited.
		/// </summary>
		public event EventHandler<WorkflowStateEventArgs> StateExit;

		/// <summary>
		/// Fired just before the workflow is saved by the persistence service.
		/// </summary>
		public event EventHandler<WorkflowEventArgs> BeforeSave;

		/// <summary>
		/// Fired after the workflow has completed.
		/// </summary>
		public event EventHandler<WorkflowEventArgs> Completed;

		/// <summary>
		/// Fired when an exception is incountered by the workflow.
		/// </summary>
		public event EventHandler<WorkflowExceptionEventArgs> UnhandledException;
	}
}
